import {
  NavigationActions,
  NavigationContainerComponent,
  StackActions,
  NavigationNavigateAction,
  NavigationParams,
  NavigationPopToTopActionPayload,
  NavigationAction,
  NavigationRoute
} from 'react-navigation';
import Log from '../common/log';

const THROTTLE_TIME = 300;

let navigator: NavigationContainerComponent | null;
let routes: NavigationRoute[];
let initDispatchTime = 0;

const setTopLevelNavigator = (
  navigatorRef: NavigationContainerComponent | null
): void => {
  navigator = navigatorRef;
};

const setRoutes = (value: NavigationRoute[]): void => {
  routes = value;
};

export const getRoutes = (): NavigationRoute[] => {
  return routes;
};

const dispatch = (action: NavigationAction): boolean => {
  if (navigator) {
    return navigator.dispatch(action);
  }
  return false;
};

const manage = (callback: any): ((...param: any) => void) => (
  ...param
): void => {
  const dispatchTime = Date.now();
  if (dispatchTime - initDispatchTime < THROTTLE_TIME) {
    Log.e('dispatch', JSON.stringify({ type: 'throttle' }));
    return;
  }
  initDispatchTime = dispatchTime;
  callback(...param);
};

const goBack = (routeName?: string | null): boolean => {
  let actualizedKey = null;
  if (routeName) {
    const routeIndex = routes.findIndex(
      (route): boolean => route.routeName === routeName
    );
    if (routeIndex !== -1) {
      if (routeIndex + 1 !== routes.length) {
        actualizedKey = routes[routeIndex + 1].key;
      }
    }
  }
  return dispatch(NavigationActions.back({ key: actualizedKey }));
};

const navigate = (
  navigateTo: string,
  params?: NavigationParams,
  action?: NavigationNavigateAction
): boolean => {
  if (typeof navigateTo === 'string') {
    return dispatch(
      NavigationActions.navigate({
        routeName: navigateTo,
        params,
        action
      })
    );
  }
  return false;
};

const pop = (n?: number, immediate?: boolean): boolean =>
  dispatch(
    StackActions.pop({
      n,
      immediate
    })
  );

const popToTop = (params?: NavigationPopToTopActionPayload): boolean =>
  dispatch(StackActions.popToTop(params));

const push = (
  routeName: string,
  params?: NavigationParams,
  action?: NavigationNavigateAction
): boolean =>
  dispatch(
    StackActions.push({
      routeName,
      params,
      action
    })
  );

const replace = (
  replaceWith: string,
  params?: NavigationParams,
  action?: NavigationNavigateAction,
  newKey?: string
): boolean => {
  if (typeof replaceWith === 'string') {
    return dispatch(
      StackActions.replace({
        routeName: replaceWith,
        params,
        action,
        key: undefined,
        newKey
      })
    );
  }
  return false;
};

const reset = (actions: NavigationNavigateAction[], index?: number): boolean =>
  dispatch(
    StackActions.reset({
      actions,
      index: index == null ? actions.length - 1 : index,
      key: undefined
    })
  );

const dismiss = (): boolean =>
  dispatch(
    NavigationActions.back({
      key: undefined
    })
  );

const setParams = (params: NavigationParams): boolean =>
  dispatch(NavigationActions.setParams({ params, key: '' }));

const goBackTo = (routeName: string, params?: NavigationParams): boolean => {
  const haveRoute =
    routes.findIndex((route): boolean => route.routeName === routeName) !== -1;
  if (haveRoute) {
    return goBack(routeName);
  }
  goBack();
  push(routeName, params);
  return true;
};

const goBackReplace = (
  backRouteName: string,
  replaceRouteName: string,
  params?: NavigationParams
): boolean => {
  const haveRoute =
    routes.findIndex((route): boolean => route.routeName === backRouteName) !==
    -1;
  if (!haveRoute) {
    return replace(replaceRouteName, params);
  }
  goBack(backRouteName);
  replace(replaceRouteName, params);
  return true;
};

const findRoute = (routeName: string): boolean =>
  routes.findIndex((route): boolean => route.routeName === routeName) !== -1;

const getCurrentRoute = (): NavigationRoute => {
  if (routes && routes.length > 0) {
    return routes[routes.length - 1];
  }
  return {
    key: '',
    index: 0,
    routeName: 'Entrance',
    routes: [],
    isTransitioning: false
  };
};

const haveCurrentRoute = (): boolean => routes && routes.length > 0;

const getRouteList = (): NavigationRoute[] => routes;

export default {
  setTopLevelNavigator,
  setRoutes,
  dispatch,
  setParams,
  findRoute,
  goBack: manage(goBack),
  navigate: manage(navigate),
  pop: manage(pop),
  popToTop: manage(popToTop),
  push: manage(push),
  replace: manage(replace),
  reset,
  dismiss: manage(dismiss),
  goBackTo: manage(goBackTo),
  goBackReplace: manage(goBackReplace),
  getCurrentRoute,
  haveCurrentRoute,
  getRouteList
};
